﻿using System;
using System.Linq;
using System.Reflection;
using Microsoft.Practices.Composite.Modularity;
using Microsoft.Practices.Unity;
using PrismNavigationFramework.Infrastructure;
using PrismNavigationFramework.Navigation;

namespace PrismNavigationFramework.MVVM
{
    public class ViewRegistrationService : IViewRegistrationService
    {
        private readonly IUnityContainer _unityContainer;
        private readonly IViewToUrlMappingRegistrationService _viewToUrlMappingService;
        private readonly IModuleNamesRegistrationService _moduleNamesRegistrationService;

        public ViewRegistrationService(IUnityContainer unityContainer, IViewToUrlMappingRegistrationService viewToUrlMappingService, IModuleNamesRegistrationService moduleNamesRegistrationService)
        {
            _unityContainer = unityContainer;
            _viewToUrlMappingService = viewToUrlMappingService;
            _moduleNamesRegistrationService = moduleNamesRegistrationService;
        }

        private void RegisterDefaultViewPath(Type viewType)
        {
            if (viewType == null)
            {
                throw new InvalidOperationException("You cannot register Default View Path for null type.");
            }

            var fullModuleName = viewType.Assembly.FullName;

            if (viewType.IsInterface)
            {
                var realViewType = FindTypeImplementingInterface(viewType);
                viewType = realViewType;
            }

            if (!string.IsNullOrEmpty(fullModuleName))
            {
                var positionOfComma = fullModuleName.IndexOf(",");
                var moduleName = fullModuleName.Substring(0, positionOfComma);

                var moduleNameWithoutWordModule = moduleName.Replace("Module", "");

                var viewNameWithoutWordView = viewType.Name.Replace("View", "");

                var shortDefaultViewUrl = string.Format("/{0}/{1}", moduleNameWithoutWordModule, viewNameWithoutWordView);

                this._viewToUrlMappingService.RegisterViewUrl(viewType, shortDefaultViewUrl);
            }
        }

        private void RegisterViewsModuleDefaultUrlFriendlyNames(Type viewType)
        {
            var fullModuleName = viewType.Assembly.FullName;

            if (!string.IsNullOrEmpty(fullModuleName))
            {
                var moduleInitializerType =
                    viewType.Assembly.GetTypes().Where(p => typeof(IModule).IsAssignableFrom(p)).FirstOrDefault();

                if (moduleInitializerType == null)
                {
                    // if this type does not have type implementing IModule then its not valid module
                    return;
                }

                var realModuleName = moduleInitializerType.FullName;

                var positionOfComma = fullModuleName.IndexOf(",");

                var moduleName = fullModuleName.Substring(0, positionOfComma);
                var moduleNameWithoutWordModule = moduleName.Replace("Module", "");

                this._moduleNamesRegistrationService.RegisterModuleName(moduleName, realModuleName);
                this._moduleNamesRegistrationService.RegisterModuleName(moduleNameWithoutWordModule, realModuleName);

            }            
        }

        private void RegisterViewsDefaultUrlAndItsModuleDefaultUrlFriendlyNames(Type viewType)
        {
            this.RegisterDefaultViewPath(viewType);
            this.RegisterViewsModuleDefaultUrlFriendlyNames(viewType);
        }


        private Type FindGenericSubclassViewModelType(Type type)
        {
            while (type != typeof(object) && type != null)
            {
                var cur = type.IsGenericType ? type.GetGenericTypeDefinition() : type;
                if (typeof(ViewModel<>) == cur)
                {
                    return type;
                }
                type = type.BaseType;
            }
            return null;
        }

        public void RegisterViewsAndTheirViewModels()
        {
            var assembly = Assembly.GetCallingAssembly();

            var typesThatAreViewModels =
                assembly.GetTypes().Where(p => typeof (IAcceptView).IsAssignableFrom(p));

            foreach (var viewModelType in typesThatAreViewModels)
            {
                var genericViewModelType = this.FindGenericSubclassViewModelType(viewModelType);
                if (genericViewModelType != null)
                {
                    this.RegisterViewAndViewModel(genericViewModelType.GetGenericArguments()[0], viewModelType);
                }
            }
         }

        public void RegisterViewAndViewModel(Type viewInterfaceType, Type viewType, Type viewModelType)
        {
            this._unityContainer.RegisterType(viewInterfaceType, viewType);
            var viewModelGenericTypeWithInterface = typeof(ViewModel<>).MakeGenericType(viewInterfaceType);
            this._unityContainer.RegisterType(viewModelGenericTypeWithInterface, viewModelType);

            this.RegisterViewsDefaultUrlAndItsModuleDefaultUrlFriendlyNames(viewType);
        }

        private static Type FindTypeImplementingInterface(Type interfaceThatNeedsToBeImplemented)
        {
            if (interfaceThatNeedsToBeImplemented.IsInterface)
            {
                var viewInterfaceType = interfaceThatNeedsToBeImplemented;
                var realViewTypes = interfaceThatNeedsToBeImplemented.Assembly.GetTypes().Where((p) => p != viewInterfaceType && viewInterfaceType.IsAssignableFrom(p)).ToList();

                if (!realViewTypes.Any())
                {
                    throw new InvalidOperationException(string.Format("Cannot find type implementing interface {0}", interfaceThatNeedsToBeImplemented.Name));                    
                }

                if (realViewTypes.Count > 1)
                {
                    throw new InvalidOperationException(string.Format("More than one type is implementing {0} interface in Assembly {1}.",interfaceThatNeedsToBeImplemented.Name, interfaceThatNeedsToBeImplemented.Assembly.GetName()));
                }

                return realViewTypes.First();
            }

            throw new InvalidOperationException(string.Format("Type {0} is not Interface.", interfaceThatNeedsToBeImplemented.Name));                                    
        }
    

        public void RegisterViewAndViewModel(Type viewType, Type viewModelType)
        {
            Type implementingType = null;

            if (viewType.IsInterface)
            {
                // if its interface we need to find real implementation and register that for the interface
                implementingType = FindTypeImplementingInterface(viewType);
                this._unityContainer.RegisterType(viewType, implementingType);                
            }
            else
            {
                this._unityContainer.RegisterType(viewType);                
            }

            var viewModelGenericType = typeof(ViewModel<>).MakeGenericType(viewType);
            this._unityContainer.RegisterType(viewModelGenericType, viewModelType);

            if (implementingType != null)
            {
                viewModelGenericType = typeof(ViewModel<>).MakeGenericType(implementingType);
                this._unityContainer.RegisterType(viewModelGenericType, viewModelType);                
            }

            this.RegisterViewsDefaultUrlAndItsModuleDefaultUrlFriendlyNames(viewType);
        }

        public void RegisterViewAndViewModel(Type viewType, Type viewModelType, string viewUrl)
        {
            this.RegisterViewAndViewModel(viewType, viewModelType);
            this._viewToUrlMappingService.RegisterViewUrl(viewType, viewUrl);
        }

        public void RegisterViewAndViewModel<TViewType, TViewModelType>()
        {
            this.RegisterViewAndViewModel(typeof(TViewType), typeof(TViewModelType));
        }

        public void RegisterViewAndViewModel<TViewType, TViewModelType>(string viewUrl)
        {
            this.RegisterViewAndViewModel(typeof(TViewType), typeof(TViewModelType), viewUrl);
        }

        public void RegisterViewAndViewModel<TViewInterfaceType, TViewType, TViewModelType>()
        {
            this.RegisterViewAndViewModel(typeof(TViewInterfaceType), typeof(TViewType), typeof(TViewModelType));
        }

        public void RegisterViewAndViewModel(Type viewInterfaceType, Type viewType, Type viewModelType, string viewUrl)
        {
            this.RegisterViewAndViewModel(viewInterfaceType, viewType, viewModelType);
            this._viewToUrlMappingService.RegisterViewUrl(viewType, viewUrl);
        }

        public void RegisterViewAndViewModel<TViewInterfaceType, TViewType, TViewModelType>(string viewUrl)
        {
            this.RegisterViewAndViewModel(typeof (TViewInterfaceType), typeof (TViewType), typeof (TViewModelType),
                                          viewUrl);
        }


    }
}